import random
import numpy as np
from ortools.sat.python import cp_model

def generate_data():
#随机生成数据

    hot = [0, 1, 2]
    layers = 37
    probability = 0.5

    # 定义货格的层次关系，使用二维列表表示货格布局，即模板号的排列规则
    each_row = ['10'] * 4 + ['8'] * 1 + ['10'] * 4 + ['6'] * 2 + ['10'] * 3 + ['8'] * 1 + ['10'] * 4 + ['6'] * 1 + [
        '10'] * 2 + ['8'] * 3 + ['10'] * 4 + ['4'] * 1 + ['10'] * 5 + ['6'] * 2 + \
               ['10'] * 4 + ['8'] * 1 + ['10'] * 4 + ['6'] * 2 + ['10'] * 3 + ['8'] * 1 + ['10'] * 4 + ['6'] * 1 + [
                   '10'] * 2 + ['8'] * 3 + ['10'] * 4 + ['4'] * 1 + ['10'] * 5 + ['6'] * 1
    cargo_grid_mapping = [each_row] * layers

    # 定义不同模板号的可放置的货物类型，以及每种货物类型最多放多少货物
    template_mapping = {"265": {
        "10": {"265*600*400": 10, "265*600*315": 12, "265*400*400": 15, "265*400*315": 18, "265*400*255": 21},
        "8": {"265*600*400": 8, "265*400*400": 12, "265*400*350": 12, "265*400*255": 15},
        "6": {"265*600*400": 6, "265*400*400": 9, "265*400*350": 12},
        "4": {"265*600*400": 4, "265*400*400": 6}
    },
        "330": {
            "10": {"330*600*400": 10, "330*600*315": 12, "330*400*400": 15, "330*400*315": 18, "330*400*255": 21},
            "8": {"330*600*400": 8, "330*400*400": 12, "330*400*350": 12, "330*400*255": 15},
            "6": {"330*600*400": 6, "330*400*400": 9, "330*400*350": 12},
            "4": {"330*600*400": 4, "330*400*400": 6}
        },
        "400": {
            "10": {"400*600*400": 10, "400*600*315": 12, "400*400*400": 15, "400*400*315": 18, "400*400*255": 21},
            "8": {"400*600*400": 8, "400*400*400": 12, "400*400*350": 12, "400*400*255": 15},
            "6": {"400*600*400": 6, "400*400*400": 9, "400*400*350": 12},
            "4": {"400*600*400": 4, "400*400*400": 6}
        },
        "500": {
            "10": {"500*600*400": 10, "500*600*315": 12, "500*400*400": 15, "500*400*315": 18, "500*400*255": 21},
            "8": {"500*600*400": 8, "500*400*400": 12, "500*400*350": 12, "500*400*255": 15},
            "6": {"500*600*400": 6, "500*400*400": 9, "500*400*350": 12},
            "4": {"500*600*400": 4, "500*400*400": 6}
        }
    }

    high = ['500', '330', '330', '400', '330', '330', '265', '265', '265', '400', '330', '330', '265', '265', '265',
            '400',
            '330', '330', '265', '265', '265', '400', '330', '330', '265', '265', '265', '400', '330', '330', '265',
            '265',
            '265', '400', '265', '265', '265']

    # # 将所有类型统一到一个字典
    # template_mapping_combined = {}
    # for key, value in template_mapping.items():
    #     template_mapping_combined.update(value)
    # # 将所有类型名整理到一个列表
    # available_goods_out = list(template_mapping_combined.keys())
    # # 定义一个空二维数组
    inner_keys = []
    for key1 in template_mapping:
        for key2 in template_mapping[key1]:
            inner_keys.extend(template_mapping[key1][key2].keys())
    # 将所有类型名整理到一个列表
    available_goods_out = []
    for key in inner_keys:
        if key not in available_goods_out:
            available_goods_out.append(key)
    print(available_goods_out)

    # 计算所有模板的货量最大值总和
    a = template_mapping['265']
    sum_total = 0
    for key, value in a.items():
        for key_1, value_1 in value.items():
            sum_total = sum_total + value_1
    print(sum_total)

    result = np.zeros((layers, len(each_row)), dtype=object)
    # 随机为每个货格选择货物类型和货物数量
    for row in range(layers):
        for grid in range(len(each_row)):
            template = cargo_grid_mapping[row][grid]
            if random.random() < probability:
                available_goods = list(template_mapping[high[row]][template].keys())
                selected_goods = random.choice(available_goods)
                max_quantity = template_mapping[high[row]][template][selected_goods]
                selected_quantity = random.randint(1, max_quantity)
                result[row, grid] = (
                layers - row, grid + 1, template, selected_goods, selected_quantity, random.choice(hot), high[row])
            else:
                result[row, grid] = (layers - row, grid + 1, template, 0, 0, random.choice(hot), high[row])

    # 将结果展开为一维
    result_flatten = result.flatten()

    # 打印结果，输出为一个二维列表，列表元素为元组,数组内的元素为元组（层数，货格序号，货格号，选取的货物类型，货量，热度，高度）
    for i in range(layers):
        print(result[i, :])

    # 计算货格总数
    element_count = result.size
    print(element_count)

    return result_flatten, element_count, available_goods_out, template_mapping, sum_total

def solve(result_flatten, element_count, available_goods_out, template_mapping, sum_total):
# 求解
    '''
    :param result_flatten:一个一维列表，列表中的元素为元组，从货架顶自左向右，自上而下遍历所有货格，每个元组内的元素为：（层数，货格序号，货格号，选取的货物类型，货量，热度，高度）
    :param element_count:货格总数
    :param available_goods_out:一个一维列表，列表元素为该货架所有的模板类型，格式为“高度*长*宽”
    :param template_mapping:一个字典，定义不同模板号的可放置的货物类型，以及每种货物类型最多放多少货物
    :param sum_total:不考虑高度，所有模板最大放货量的总和，此变量主要是为了给求解器一个范围值，也可用100代替
    :return:每个货格选择的模板
    '''
    # 创建参数nij
    N = element_count
    M = len(available_goods_out)
    nij = np.zeros((N + 1, M + 1), dtype=object)  # 初始化 nij 数组
    nij[0, 1:] = available_goods_out
    nij[1:, 0] = list(range(1, element_count + 1))
    for i in range(1, N + 1):  # 将i的范围从1到N+1
        for j in range(1, M + 1):  # 将j的范围从1到M+1
            if result_flatten[i - 1][3] == 0:  # 如果货格 i 中没有货
                if available_goods_out[j - 1] in template_mapping[result_flatten[i - 1][6]][result_flatten[i - 1][2]]:
                    nij[i][j] = template_mapping[result_flatten[i - 1][6]][result_flatten[i - 1][2]][
                        available_goods_out[j - 1]]
                else:
                    nij[i][j] = 0
            else:  # 如果货格 i 中已经有货，货物类型为 selected_goods
                if result_flatten[i - 1][3] == available_goods_out[j - 1]:
                    max_quantity = template_mapping[result_flatten[i - 1][6]][result_flatten[i - 1][2]][
                        result_flatten[i - 1][3]]
                    selected_quantity = result_flatten[i - 1][4]
                    nij[i][j] = max_quantity - selected_quantity
                else:
                    nij[i][j] = 0  # 其他类型的货物不能放在已经有货的货格中

    # 索引nij[1][1]对应于第一行第一列
    for i in range(N + 1):
        print(nij[i, :])

    # 创建参数mij
    mij = np.zeros((N + 1, M + 1), dtype=object)  # 初始化 mij 数组
    mij[0, 1:] = available_goods_out
    mij[1:, 0] = list(range(1, element_count + 1))
    d = 3
    for i in range(1, N + 1):  # 将i的范围从1到N+1
        for j in range(1, M + 1):  # 将j的范围从1到M+1
            if result_flatten[i - 1][3] == 0:  # 如果货格 i 中没有货
                mij[i][j] = 0
            else:
                if result_flatten[i - 1][3] == available_goods_out[j - 1]:
                    if result_flatten[i - 1][4] <= d:
                        mij[i][j] = 1
                    else:
                        mij[i][j] = 0
                else:
                    mij[i][j] = 0

    for i in range(N + 1):
        print(mij[i, :])

    # 创建参数ahj,amj,alj,bj
    # random_integer = [random.randint(1, 10) for _ in range(len(available_goods_out)*3)]
    # random_integer_sum = sum(random_integer)
    # random_integer_1 = random_integer[0: len(available_goods_out)]
    # random_integer_2 = random_integer[len(available_goods_out): len(available_goods_out)*2]
    # random_integer_3 = random_integer[len(available_goods_out)*2: len(available_goods_out)*3]
    # random_integer_h = [i/random_integer_sum for i in random_integer_1]
    # random_integer_m = [i/random_integer_sum for i in random_integer_2]
    # random_integer_l = [i/random_integer_sum for i in random_integer_3]

    ahj = np.array(
        [0, 0, 0.017 / 3, 0, 0.03 / 3, 0.096 / 3, 0, 0.005 / 3, 0.022 / 3, 0.001 / 3, 0.448 / 3, 0, 0, 0.158 / 3, 0,
         0.201 / 3, 0,
         0, 0, 0.013 / 3, 0, 0.009 / 3, 0, 0, 0])

    amj = np.array(
        [0, 0, 0.017 / 3, 0, 0.03 / 3, 0.096 / 3, 0, 0.005 / 3, 0.022 / 3, 0.001 / 3, 0.448 / 3, 0, 0, 0.158 / 3, 0,
         0.201 / 3, 0,
         0, 0, 0.013 / 3, 0, 0.009 / 3, 0, 0, 0])

    alj = np.array(
        [0, 0, 0.017 / 3, 0, 0.03 / 3, 0.096 / 3, 0, 0.005 / 3, 0.022 / 3, 0.001 / 3, 0.448 / 3, 0, 0, 0.158 / 3, 0,
         0.201 / 3, 0,
         0, 0, 0.013 / 3, 0, 0.009 / 3, 0, 0, 0])

    bj = np.zeros(M + 1, dtype=float)
    for j in range(1, M + 1):
        bj[j] = ahj[j] + amj[j] + alj[j]

    print(bj)
    print(np.sum(bj))

    # 创建参数khi,kmi,kli
    khi = np.zeros(N + 1, dtype=int)
    for i in range(1, N + 1):
        if result_flatten[i - 1][5] == 2:
            khi[i] = 1
        else:
            khi[i] = 0

    kmi = np.zeros(N + 1, dtype=int)
    for i in range(1, N + 1):
        if result_flatten[i - 1][5] == 1:
            kmi[i] = 1
        else:
            kmi[i] = 0

    kli = np.zeros(N + 1, dtype=int)
    for i in range(1, N + 1):
        if result_flatten[i - 1][5] == 0:
            kli[i] = 1
        else:
            kli[i] = 0

    # 创建参数o(有货的货格总数）
    o = 0
    for i in range(1, N + 1):  # 将i的范围从1到N+1
        if result_flatten[i - 1][3] != 0:
            o += 1
    print("o=", o)

    # 创建参数e
    e = 0.05

    # 创建参数Q
    Q = 100000

    # 创建参数f
    f = 0.0

    # 创建cp-sat模型
    model = cp_model.CpModel()

    # 变量Xij表示是否选中货格i的货物类型j，采用布尔类型
    X = {}
    for i in range(1, N + 1):
        for j in range(1, M + 1):
            X[(i, j)] = model.NewBoolVar(f'X_{i}_{j}')

    # 约束条件：模板调整涉及到的货量尽量少
    model.Add(
        sum(mij[i][j] - X[(i, j)] for i in range(1, N + 1) for j in range(1, M + 1) if mij[i][j] == 1) <= int(e * o))

    # 约束条件 - 每个货格只能选择一个货物类型
    for i in range(1, N + 1):
        model.Add(sum(X[(i, j)] for j in range(1, M + 1)) == 1)

    # 计算总货位数
    total_cargo_space = model.NewIntVar(0, sum_total * N, 'total_cargo_space')

    # 添加约束条件，计算总货位数
    model.Add(total_cargo_space == sum(nij[i][j] * X[(i, j)] for i in range(1, N + 1) for j in range(1, M + 1)))

    # 约束条件：对各包规,高,中，低热度货位数满足需求
    for j in range(1, M + 1):
        model.Add(Q * sum(
            X[(i, j)] * khi[i] * nij[i][j] for i in range(1, N + 1) if mij[i][j] == 0) - total_cargo_space * int(
            Q * f * ahj[j]) >= 0)

    for j in range(1, M + 1):
        model.Add(Q * sum(
            X[(i, j)] * kmi[i] * nij[i][j] for i in range(1, N + 1) if mij[i][j] == 0) - total_cargo_space * int(
            Q * f * amj[j]) >= 0)

    for j in range(1, M + 1):
        model.Add(Q * sum(
            X[(i, j)] * kli[i] * nij[i][j] for i in range(1, N + 1) if mij[i][j] == 0) - total_cargo_space * int(
            Q * f * alj[j]) >= 0)
    #
    # # 约束条件：对各包规，提供不同尺寸的模板需要满足:
    for j in range(1, M + 1):
        model.Add(
            (sum(X[(i, j)] * khi[i] * nij[i][j] for i in range(1, N + 1)) +
             sum(X[(i, j)] * kmi[i] * nij[i][j] for i in range(1, N + 1)) +
             sum(X[(i, j)] * kli[i] * nij[i][j] for i in range(1, N + 1))
             ) * Q - total_cargo_space * int(Q * f * bj[j]) + sum(mij[i][j] * nij[i][j] for i in range(1, N + 1)) >= 0
        )

    # 优化目标 - 最大化总货位数
    model.Maximize(total_cargo_space)

    # 创建一个cp-sat求解器
    solver = cp_model.CpSolver()
    solver.parameters.log_search_progress = True
    solver.parameters.max_time_in_seconds = 10
    # 解决优化问题
    status = solver.Solve(model)

    # 输出结果
    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        selected_templates = {}
        for i in range(1, N + 1):
            for j in range(1, M + 1):
                if solver.Value(X[(i, j)]) == 1:
                    selected_templates[i] = available_goods_out[j - 1]
        print("最大化的货位数为:", solver.ObjectiveValue())
        print("每个货格选择的模板：", selected_templates)
        return selected_templates
    else:
        print("无法找到最优解")



if __name__ == '__main__':
    result_flatten, element_count, available_goods_out, template_mapping, sum_total = generate_data()
    solve(result_flatten, element_count, available_goods_out, template_mapping, sum_total)


